#!/usr/bin/python
'''
@name owl2pvoutputorg.py
@description Listens to OWL multicast messages from OWL intuition network gateway and uploads to pvoutput.org
@author Rick Crook
@license GPL
@version 0.01 (2 Feb 2013)
@nextrelease logging, unit test, exception handling, return to q if not uploaded
'''

import Queue,threading
import socket,struct
import xml.etree.ElementTree as ET
from datetime import datetime
import urllib,urllib2
import time
import sys, getopt


class owlListener(threading.Thread):
    """listener for OWL multicast messages on local network"""
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.stopped = False

        # Join OWL multicast group
        MCAST_GRP,MCAST_PORT = '224.192.32.19', 22600
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
        self.sock.bind(('', MCAST_PORT))
        mreq = struct.pack ("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
        self.sock.setsockopt (socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    def _get_weather(self,xml):
        ''' parses OWL xml, returns temperature to the nearest c
        sample xml:
            <weather id='00A0C914C851' code='263'>
            <temperature>19.00</temperature>
            <text>Patchy light drizzle</text>
            </weather>  '''
        temp = ''
        root = ET.fromstring(xml)
        for weather in root.iter('weather'):
            temp = str("{0:.1f}".format(float(weather.find('temperature').text)))
            print ('Temperature set to %s' % temp)
        return [temp]

    def _get_solar(self,xml):
        ''' parses OWL xml, summing all channels, rounding power and energy to the nearest watt
        sample xml:
            <solar id='00A0C914C851'>
            <current><generating units='w'>2.00</generating><exporting units='w'>1.00</exporting></current>
            <day><generated units='wh'>4.00</generated><exported units='wh'>3.00</exported></day>
            </solar>'''

        root = ET.fromstring(xml)
        energy,power = '-1','-1'
        for solar in root.iter('solar'):
            energy = solar.find('day').find('generated').text
            power = solar.find('current').find('generating').text
        
        return [energy,power]

    def _get_electricity(self,xml):
        ''' parses OWL xml, summing all channels, rounding power and energy to the nearest watt
        sample xml:
            <electricity id='443719999999'>
            <signal rssi='-71' lqi='127'/>
            <battery level='100%'/>
            <chan id='0'><curr units='w'>483.00</curr><day units='wh'>10244.99</day></chan>
            <chan id='1'><curr units='w'>0.00</curr><day units='wh'>0.00</day></chan>
            <chan id='2'><curr units='w'>0.00</curr><day units='wh'>0.00</day></chan>
            </electricity>'''

        root = ET.fromstring(xml)
        powerc,energyc = '-1','-1'
        for electricity in root.iter('electricity'):
            #print electricity.tag, electricity.attrib, electricity.text
            powerc,energyc = 0,0
            for chan in electricity.iter('chan'):
                 powerc = powerc + int(float(chan.find('curr').text))
                 energyc = energyc + int(float(chan.find('day').text))
            powerc,energyc = str(powerc), str(energyc)
        return [energyc,powerc]

    def _fix_midnight(self,last_status,status):
        ''' OWL resets energy count at midnight, their midnight may not be our midnight'''
        if last_status == []:
            return status,status

        # same day as last status and energy use is higher, update last_status = status
        if last_status[0] == status[0] and int(status[4]) > int(last_status[4]):
                #print 'same day as last status and energy use is higher, update last_status = status'
                return status,status

        # same day as last status and energy use is lower, update status[energy] = last_status[energy]
        if last_status[0] == status[0] and int(status[4]) < int(last_status[4]):
                #print 'same day as last status and energy use is lower, update status[energy] = last_status[energy]'
                status[4] = last_status[4]
                return last_status,status
        
        # different day as last status and energy use is higher, update status[energy] = 0
        if last_status[0] != status[0] and int(status[4]) > int(last_status[4]):    
                #print 'different day as last status and energy use is higher, update status[energy] = 0'   
                status[4] = '0'
                return last_status,status

        # different day as last status and energy use is lower, update last_status = status
        if last_status[0] != status[0] and int(status[4]) < int(last_status[4]):
                #print 'different day as last status and energy use is lower, update last_status = status'          
                return status,status

        #print 'last_status = status'
        return status,status

    def run(self):
        # Read xml messages from OWL, parse to dict and add to queue
        last_status = []
        while not self.stopped:
            msg = self.sock.recv(800)
            now = datetime.now()
            status = [now.strftime('%Y%m%d'), now.strftime('%H:%M')]
            status += self._get_solar(msg)
            status += self._get_electricity(msg)
            
            temp = self._get_weather(msg)
            if temp != ['']:
                status += temp
                last_status += temp
            else:
                last_status,status = self._fix_midnight(last_status,status)
                print ("New status from OWL %s" % (status))
                self.queue.put(status)

    def stop(self):
        self.stopped = True

class pvoutputDispatcher(threading.Thread):
    """dispatcher for pvoutput.org"""
    def __init__(self, queue, apikey, systemid):
        threading.Thread.__init__(self)
        self.queue = queue
        self.apikey = apikey
        self.systemid = systemid
        self.stopped = False

    def run(self):
        while not self.stopped:
            time.sleep(60) # pvoutput.org maximum 60 uploads per hour
            data = ''
            while not self.queue.empty():
                status = self.queue.get()
                data = ','.join(status) + ';'
                self.queue.task_done()
            if data != '':
                response = self._upload(data)
                print ("Status sent to pvoutput.org: %s : %s" % (status,response))

    def _upload(self,data):
        # from http://pvoutput.org/help.html#api-spec
        pvoutput_service_url = 'http://pvoutput.org/service/r2/addbatchstatus.jsp'
        # data = date(yyyymmdd), time(hh:mm), energy gen, power gen, energy con, power con, temp, volt
        # use -1 if you don't have the data. You must have an energy or power data  
        values = {'data' : data}
        headers = {"X-Pvoutput-Apikey": self.apikey, "X-Pvoutput-SystemId": self.systemid}

        data = urllib.urlencode(values)
        req = urllib2.Request(pvoutput_service_url, data, headers)
        try:
            response = urllib2.urlopen(req)
        except URLError, e:
            print (e.code, e.reason)
        response_data = response.read()
        return response.code == 200

    def stop(self):
        self.stopped = True

def main(argv):
    # Parse the command line options passed
    apikey = ''
    systemid = ''
    try:
        opts, args = getopt.getopt(argv,"hk:i:",["key=","id="])
    except getopt.GetoptError:
        print ('owl2pvoutputorg.py -k <apikey> -i <systemid>')
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print ('owl2pvoutputorg.py -k <apikey> -i <systemid>')
            sys.exit()
        elif opt in ("-k", "--key"):
            apikey = arg
        elif opt in ("-i", "--id"):
            systemid = arg

    # Create a queue, OWL listener and pvoutput dispatcher threads
    print 'Starting owl2pvoutputorg for pvoutput.systemid %s' % systemid
    queue = Queue.Queue()

    owl = owlListener(queue)
    owl.start()

    pvoutput = pvoutputDispatcher(queue, apikey, systemid)
    pvoutput.start()

    # Wait here until something asks us to stop
    threadcount = threading.activeCount()
    while threading.activeCount() == threadcount:
        time.sleep(1)

    owl.stop()
    pvoutput.stop()
    print ('Stopping owl2pvoutputorg for pvoutput.systemid %s' % systemid)

if __name__ == "__main__":
   main(sys.argv[1:])